package org.mozhu.mboot.backend.security;

import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.mozhu.mboot.backend.AjaxResponse;
import org.mozhu.mboot.backend.HttpServlets;
import org.mozhu.mboot.backend.system.account.entity.User;
import org.mozhu.mboot.backend.system.account.service.UserService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class JWTAuthenticationFilter extends OncePerRequestFilter {

    JWTService jwtService;

    UserService userService;

    public JWTAuthenticationFilter(JWTService jwtService, UserService userService) {
        this.jwtService = jwtService;
        this.userService = userService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // If the Http request is OPTIONS then just return the status code 200
        // which is HttpServletResponse.SC_OK in this code
        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            filterChain.doFilter(request, response);
            return;
        }
        // Except OPTIONS, other request should be checked by JWT
        try {
            String token = getToken(request);
            UsernamePasswordAuthenticationToken authentication = getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
            log.error("Invalid token", e);
            HttpServlets.renderResponse(response, AjaxResponse.fail("InvalidToken", "Token is invalid"));
            return;
        } catch (ExpiredJwtException e) {
            HttpServlets.renderResponse(response, AjaxResponse.fail("ExpiredToken", "Token is expired"));
            return;
        }
        filterChain.doFilter(request, response);
    }

    private String getToken(HttpServletRequest request) {
        // Get authorization from Http request
        final String authHeader = request.getHeader("authorization");

        // Check the authorization, check if the token is started by "Bearer "
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            throw new IllegalArgumentException("Missing or invalid Authorization header");
        }

        // Then get the JWT token from authorization
        return authHeader.substring(7);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String token) {
        Claims claims = jwtService.parseToken(token);
        String subject = claims.getSubject();
        User user = userService.findByLoginName(subject);
        IdentifiedUserDetails identifiedUserDetails = user.userDetails();
        return new UsernamePasswordAuthenticationToken(identifiedUserDetails, null, identifiedUserDetails.getAuthorities());
    }

}

